#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <pthread.h>
#include <stdint.h>

/* these may be any values >= 0 */
#define NUMBER_OF_CUSTOMERS 5
#define NUMBER_OF_RESOURCES 3
/* the available amount of each resource */
int available[NUMBER_OF_RESOURCES];
/*the maximum demand of each customer */
int maximum[NUMBER_OF_CUSTOMERS][NUMBER_OF_RESOURCES];
/* the amount currently allocated to each customer */
int allocation[NUMBER_OF_CUSTOMERS][NUMBER_OF_RESOURCES];
/* the remaining need of each customer */
int need[NUMBER_OF_CUSTOMERS][NUMBER_OF_RESOURCES];
/* the finished customer */
int fuck_yeah[NUMBER_OF_CUSTOMERS];

int request_resources(int customer_num, int request[]);
int release_resources(int customer_num, int release[]);

int finish_num = 0;

pthread_mutex_t mutex;

int rand_num(int start, int end)
{
    return start + (int)(rand() / (RAND_MAX + 1.0) * (end - start));
}

void snapshot() // print all matrix
{
    int i, j;
    printf("Available:\n");
    for (i = 0; i < NUMBER_OF_RESOURCES; i++)
    {
        printf("%d ", available[i]);
    }
    printf("\nMaximum:\n");
    for (i = 0; i < NUMBER_OF_CUSTOMERS; i++)
    {
        for (j = 0; j < NUMBER_OF_RESOURCES; j++)
        {
            printf("%d ", maximum[i][j]);
        }
        printf("\n");
    }
    printf("Allocation:\n");
    for (i = 0; i < NUMBER_OF_CUSTOMERS; i++)
    {
        for (j = 0; j < NUMBER_OF_RESOURCES; j++)
        {
            printf("%d ", allocation[i][j]);
        }
        printf("\n");
    }
    printf("Need:\n");
    for (i = 0; i < NUMBER_OF_CUSTOMERS; i++)
    {
        for (j = 0; j < NUMBER_OF_RESOURCES; j++)
        {
            printf("%d ", need[i][j]);
        }
        printf("\n");
    }
}

int safe() // return 1 if safe else return 0
{
    int work[NUMBER_OF_RESOURCES];
    int i, j;
    for (i = 0; i < NUMBER_OF_RESOURCES; i++)
    {
        work[i] = available[i]; //work <= available
    }

    int finish[NUMBER_OF_CUSTOMERS]; // 0 for false, 1 for true
    for (i = 0; i < NUMBER_OF_CUSTOMERS; i++)
    {
        finish[i] = 0; // init finish array
    }
    while (1)
    {
        int flag = 1;
        for (i = 0; i < NUMBER_OF_CUSTOMERS; i++)
        {
            if (finish[i] == 0 && need[i][0] <= work[0] && need[i][1] <= work[1] && need[i][2] <= work[2]) // if not finished and all need <= work
            {
                for (j = 0; j < NUMBER_OF_RESOURCES; j++)
                {
                    work[j] += need[i][j];
                }

                finish[i] = 1;
                flag = 0;
            }
        }
        if (flag == 1)
        {
            break;
        }
    }
    int safe_flag = 1;
    for (i = 0; i < NUMBER_OF_CUSTOMERS; i++)
    {
        if (finish[i] == 0) // if any element of finish is 0 then unsafe
        {
            safe_flag = 0;
        }
    }
    if (safe_flag == 1)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

int request_resources(int customer_num, int request[]) // return 69 when request array is (0,0,0); return 0 when unsafe; return 1 when safe; return 2 when request too much.
{
    int tid = customer_num;
    int i;
    pthread_mutex_lock(&mutex);

    for (i = 0; i < NUMBER_OF_RESOURCES; i++)
    {
        request[i] = rand_num(0, need[tid][i] + 1); // generate request array randomly
    }

    if (request[0] == 0 && request[1] == 0 && request[2] == 0) // if request array is (0,0,0), then return 69
    {
        pthread_mutex_unlock(&mutex);
        return 69;
        //continue;
    }

    printf("Thread %d request:%d %d %d", tid + 1, request[0], request[1], request[2]);          // print request
    if (request[0] <= available[0] && request[1] <= available[1] && request[2] <= available[2]) // if request <= available
    {
        for (i = 0; i < NUMBER_OF_RESOURCES; i++)
        {
            available[i] -= request[i];       // available - request
            allocation[tid][i] += request[i]; // allocation + request
            need[tid][i] -= request[i];       // reamain need - request
        }

        if (need[tid][0] == 0 && need[tid][1] == 0 && need[tid][2] == 0) // if the thread complete
        {
            for (i = 0; i < NUMBER_OF_RESOURCES; i++)
            {
                available[i] += allocation[tid][i]; // release resources
            }

            printf(" this thread complete,");

            finish_num++;
            fuck_yeah[finish_num - 1] = tid + 1;

            if (finish_num == NUMBER_OF_CUSTOMERS) // if all finished
            {
                printf(" all complete!\n");
                printf("The Serial: ");
                for (i = 0; i < NUMBER_OF_CUSTOMERS; i++)
                {
                    printf("P%d ", fuck_yeah[i]);
                }
                printf("\n");
                pthread_mutex_unlock(&mutex);
                exit(0);
            }
        }
        if (safe() == 0) // if not safe then roll back
        {
            /*roll back*/
            for (i = 0; i < NUMBER_OF_RESOURCES; i++)
            {
                available[i] += request[i];       // available + request
                allocation[tid][i] -= request[i]; // allocation - request
                need[tid][i] += request[i];       // reamain need + request
            }

            printf(" unsafe ");
            pthread_mutex_unlock(&mutex);
            return 0;
        }
        else
        {
            printf(" safe ");
        }
        printf("available: %d %d %d ", available[0], available[1], available[2]);
        printf(" need: %d %d %d\n", need[tid][0], need[tid][1], need[tid][2]);
        pthread_mutex_unlock(&mutex);
        return 1;
    }
    else
    {
        printf(" too much!\n");
        pthread_mutex_unlock(&mutex);
        return 2;
    }
}

int release_resources(int customer_num, int release[])
{
    int i;
    int tid = customer_num;
    /*roll back*/
    for (i = 0; i < NUMBER_OF_RESOURCES; i++)
    {
        available[i] += release[i];       // available + release
        allocation[tid][i] -= release[i]; // allocation - release
    }
    printf("Thread %d release: ", tid + 1);
    for (i = 0; i < NUMBER_OF_RESOURCES; i++)
    {
        printf("%d ", release[i]);
    }
    printf("\n\n");
}

void *simulate(void *param)
{
    int i, state;
    int tid = (intptr_t)param; // real tid -1
    while (1)
    {
        int request_num[NUMBER_OF_RESOURCES]; // the request array

        state = request_resources(tid, request_num);

        if (state == 69)
        {
            continue;
        }
        else if (state == 1) // safe
        {
            int release_num[NUMBER_OF_RESOURCES]; // the release array
            for (i = 0; i < NUMBER_OF_RESOURCES; i++)
            {
                release_num[i] = rand_num(0, allocation[tid][i]); // generate release array
            }
            release_resources(tid, release_num);
        }
    }
}

int main(int argc, char *argv[])
{
    void *iptr = NULL;
    int a_num, b_num, c_num, i, j;
    int ret = 0;
    a_num = atoi(argv[1]);
    b_num = atoi(argv[2]);
    c_num = atoi(argv[3]);
    available[0] = a_num;
    available[1] = b_num;
    available[2] = c_num;
    printf("First: %d; Second: %d; Third: %d.\n", a_num, b_num, c_num);

    pthread_t tids[NUMBER_OF_CUSTOMERS];

    srand((unsigned int)(time(NULL))); // set RAND_MAX

    pthread_mutex_init(&mutex, NULL); // init mutex lock

    for (i = 0; i < NUMBER_OF_CUSTOMERS; i++)
    {
        for (j = 0; j < NUMBER_OF_RESOURCES; j++)
        {
            maximum[i][j] = rand_num(0, available[j] + 1); // init max matrix
            allocation[i][j] = 0;                          // init allocation matrix
            need[i][j] = maximum[i][j];                    // init need matrix  (- allocation[i][j])
        }
    }

    snapshot(); // init state snapshot

    for (i = 0; i < NUMBER_OF_CUSTOMERS; i++)
    {
        ret = pthread_create(&tids[i], NULL, simulate, (void *)(intptr_t)i); // create request threads
    }

    for (i = 0; i < NUMBER_OF_CUSTOMERS; i++)
    {
        pthread_join(tids[i], NULL);
    }

    return 0;
}
